package cn.ali.mybatis.plugin;

import cn.ali.bean.PageVo;
import cn.ali.page.PageResult;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;


/**
 * Executor：拦截执行器的方法。
 * ParameterHandler：拦截参数的处理。
 * ResultHandler：拦截结果集的处理。
 * StatementHandler：拦截Sql语法构建的处理。
 */
@Intercepts({
        @Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PqsExcutorPlugin implements Interceptor {


    /**
     * 执行目标方法
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("PqsExcutorPlugin 自动以拦截器的 intercept");

        /**
         * 就是我们拦截的Executor.query(参数1,参数2,参数3,参数4)
         * 参数1:mappedStatement
         * 参数2:sql的参数
         * 参数3:RowBounds
         * 参数4:ResultHandler
         */
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object sqlParam = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];

        /** 通过 sqlParam 获取 boundSql */
        BoundSql boundSql = mappedStatement.getBoundSql(sqlParam);
//        Object parameterObject = boundSql.getParameterObject();


        Executor executor = (Executor) invocation.getTarget();


        /**
         * 判断是否是继承PageVo来判断是否需要进行分页
         */
        PageVo pagevo = null;
        if (null != pagevo) {

            String sql = boundSql.getSql();

            /** 拼接查询当前条件的sql的总条数 */
            String countSql = "select count(*) from (" + sql + ") a";

            /**
             * 统计总数1:
             * 思路: 通过connect,直接执行新的sql语句,获取总数
             */
            Long count2 = queryDataTotal(mappedStatement, boundSql, sqlParam, countSql);


            /**
             * 统计总数2: [分页助手也是这样写的]
             * 思路: 为新的sql语句,创建新的 mappedStatement, 然后执行 executor.query(新的 mappedStatement,....)
             */
            Long count = queryDataTotal2(mappedStatement, boundSql, sqlParam, countSql, resultHandler, executor);


            /** 拼接分页sql */
            int start = (int) (pagevo.getPageNum() == 0 ? 0 : (pagevo.getPageNum() - 1) * pagevo.getPageSize());
            String pageSql = sql + " limit " + start + " , " + pagevo.getPageSize();
            //执行分页查询的sql
            BoundSql pageBoundSql = new BoundSql(mappedStatement.getConfiguration(), pageSql, boundSql.getParameterMappings(), sqlParam);
            //执行分页查询
            //创建 count 查询的缓存 key
            CacheKey pageCache = executor.createCacheKey(mappedStatement, sqlParam, RowBounds.DEFAULT, pageBoundSql);

            List resultList = executor.query(mappedStatement, sqlParam, RowBounds.DEFAULT, resultHandler, pageCache, pageBoundSql);
            //处理新的结构
            PageResult<?> pageResult = new PageResult<List>(pagevo.getPageSize(), pagevo.getPageNum(), count.intValue(), (List) resultList);
            List<PageResult> returnResultList = new ArrayList();
            returnResultList.add(pageResult);

            return returnResultList;
        }
        return invocation.proceed();
    }

    private Long queryDataTotal2(MappedStatement mappedStatement, BoundSql boundSql, Object sqlParam, String countSql, ResultHandler resultHandler, Executor executor) throws SQLException {
        String countMsId = mappedStatement.getId() + "_COUNT";
        MappedStatement countMs = newCountMappedStatement(mappedStatement, countMsId);
        BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), sqlParam);
        Object countResultList = executor.query(countMs, sqlParam, RowBounds.DEFAULT, resultHandler, null, countBoundSql);

        Long count = ((Number) ((List) countResultList).get(0)).longValue();
        return count;
    }

    private MappedStatement newCountMappedStatement(MappedStatement ms, String newMsId) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, ms.getSqlSource(), ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        //count查询返回值int
        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Long.class, new ArrayList<ResultMapping>(0)).build();
        resultMaps.add(resultMap);
        builder.resultMaps(resultMaps);
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }


    private Long queryDataTotal(MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject, String countSql) throws SQLException {
        Connection connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();

        PreparedStatement preparedStatement = connection.prepareStatement(countSql);

        BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBoundSql);
        parameterHandler.setParameters(preparedStatement);
        //执行获得总条数
        ResultSet rs = preparedStatement.executeQuery();
        Long count = 0L;
        if (rs.next()) {
            count = rs.getLong(1);
        }
        return count;
    }


    /**
     * 对目标对象进行包装,返回一个代理对象
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        Object wrap = Plugin.wrap(target, this);
        return wrap;
    }


}
